﻿namespace Moly.misc
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using System.Security.Cryptography;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Tasks;
    using Microsoft.Win32;

    /// <summary>
    /// Utility functions.
    /// </summary>
    public static class Utility
    {
        public static void CreatePath(string dir)
        {
            var dirs = dir.Split('\\');
            var acom = dirs[0] + "\\";
            for (var i = 1; i < dirs.Length; i++)
            {
                acom = Path.Combine(acom, dirs[i]);
                if (!Directory.Exists(acom))
                    Directory.CreateDirectory(acom);
            }
        }

        public static string ComputeHash(string fullPath)
        {
            if (!File.Exists(fullPath))
                return string.Empty;

            var file = File.OpenRead(fullPath);
            var hasher = new SHA1CryptoServiceProvider();
            var hash = hasher.ComputeHash(file);
            var str = BitConverter.ToString(hash).Replace("-", String.Empty);
            file.Close();
            return str;
        }

        public static void CopyAll(string srcDir, string dstDir, bool overwrite, List<string> ignoreExt = null)
        {
            if (!Directory.Exists(srcDir))
                throw new DirectoryNotFoundException("Invalid src dir " + srcDir);

            CreatePath(dstDir);

            foreach (var file in Directory.GetFiles(srcDir))
            {
                var ext = Path.GetExtension(file).ToLower();
                if (ignoreExt != null && ignoreExt.Contains(ext))
                    continue;

                var dest = Path.Combine(dstDir, Path.GetFileName(file));
                if (File.Exists(dest) && ComputeHash(file) == ComputeHash(dest))
                    continue;

                File.Copy(file, dest, overwrite);
            }

            foreach (var dir in Directory.GetDirectories(srcDir))
            {
                if (!Path.GetFileName(dir).StartsWith("."))
                    CopyAll(dir, Path.Combine(dstDir, Path.GetFileName(dir)), overwrite);
            }
        }

        public static string ChangeExt(string file, string nwExt)
        {
            return file.Substring(0, file.LastIndexOf('.')) + "." + nwExt;
        }

        public static void Register(string path)
        {
            var mf = new ModuleFile(path);
            mf.VerifyExist();
            var temp = Environment.CurrentDirectory;
            try
            {
                Environment.CurrentDirectory = mf.Dir;
                switch (mf.BinaryType)
                {
                    case ModuleFile.ModuleType.Il:
                        RegisterIl(path);
                        break;
                    case ModuleFile.ModuleType.Com:
                        registerCOMDll(path);
                        break;
                    default:
                        throw new Exception("Can't register module of type:" + mf.BinaryType);
                }
            }
            finally
            {
                Environment.CurrentDirectory = temp;
            }
        }

        private static void registerCOMDll(string file)
        {
            //in cases like c:\windows\system32\msvbvm60.dll\3
            //we register   c:\windows\system32\msvbvm60.dll
            if (File.Exists(Path.GetDirectoryName(file)))
                file = Path.GetDirectoryName(file);

            var handle = IntPtr.Zero;
            try
            {
                handle = API.LoadLibraryEx(file, IntPtr.Zero, 0);
                if (handle == IntPtr.Zero)
                    throw new Exception(string.Format("LoadLibrary failed for registration of {0} with code={1}", file, Marshal.GetLastWin32Error()));

                try
                {
                    var code = API.DllRegisterServer(handle);
                    if (code != 0)
                        throw new Exception(string.Format("DllRegisterServer failed with error code={0} for {1}", code,
                                                          file));
                }
                catch
                {
                    ITypeLib lib = null;
                    try
                    {
                        API.LoadTypeLibEx(file, API.RegKind.RegKind_Register, out lib);
                        API.RegisterTypeLib(lib, file, "");
                    }
                    finally
                    {
                        if (lib != null)
                            Marshal.ReleaseComObject(lib);
                    }
                }
            }
            finally
            {
                if (handle != IntPtr.Zero)
                    API.FreeLibrary(handle);
            }
        }

        [Serializable]
        public class RegisterIlDomainWrapper
        {
            public RegisterIlDomainWrapper(string file)
            {
                var result = new RegisterAssembly
                {
                    CreateCodeBase = true,
                    Assemblies = new ITaskItem[] { new RegisterIlHelper(file) },
                    BuildEngine = new MyBuildEngine()
                };
                if (!result.Execute())
                    throw new Exception("Registration failed.");
            }
        }
        [Serializable]
        public class RegisterIlHelper : ITaskItem
        {
            public string FileName;

            public RegisterIlHelper(string dest)
            {
                FileName = dest;
            }

            public string GetMetadata(string metadataName)
            {
                return string.Empty;
            }

            public void SetMetadata(string metadataName, string metadataValue)
            {
            }

            public void RemoveMetadata(string metadataName)
            {

            }

            public void CopyMetadataTo(ITaskItem destinationItem)
            {

            }

            public IDictionary CloneCustomMetadata()
            {
                return null;
            }

            public string ItemSpec
            {
                get { return FileName; }
                set { FileName = value; }
            }

            public ICollection MetadataNames
            {
                get { return new List<string>(); }
            }

            public int MetadataCount
            {
                get { return 0; }
            }
        }
        [Serializable]
        public class MyBuildEngine : IBuildEngine
        {
            public bool BuildProjectFile(string projectFileName, string[] targetNames,
                                         IDictionary globalProperties,
                                         IDictionary targetOutputs)
            {
                throw new NotImplementedException();
            }

            public int ColumnNumberOfTaskNode
            {
                get { return 0; }
            }

            public bool ContinueOnError
            {
                get { return false; }
            }

            public int LineNumberOfTaskNode
            {
                get { return 0; }
            }

            public string ProjectFileOfTaskNode
            {
                get { return ""; }
            }

            public void LogCustomEvent(CustomBuildEventArgs e)
            {
                Console.WriteLine("Custom: {0}", e.Message);
            }

            public void LogErrorEvent(BuildErrorEventArgs e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }

            public void LogMessageEvent(BuildMessageEventArgs e)
            {
                Console.WriteLine("Message: {0}", e.Message);
            }

            public void LogWarningEvent(BuildWarningEventArgs e)
            {
                Console.WriteLine("Warning: {0}", e.Message);
            }
        }
        public static void RegisterIl(string dest)
        {
            try
            {
                var dll = typeof(RegisterIlDomainWrapper).Assembly.CodeBase;
                var domain = AppDomain.CreateDomain("Registration domain for " + dest);
                domain.CreateInstanceFromAndUnwrap(dll, (typeof(RegisterIlDomainWrapper)).FullName,
                    false, BindingFlags.Default, null, new object[] { dest }, null, null);
                AppDomain.Unload(domain);
            }
            catch (TargetInvocationException ex)
            {
                throw ex.InnerException;
            }
        }

        public static void Dir(string path, string pattern, List<string> result)
        {
            try
            {
                result.AddRange(Directory.GetFiles(path, pattern));
                Array.ForEach(Directory.GetDirectories(path), d => Dir(d, pattern, result));
            }
            catch (UnauthorizedAccessException)
            {
            }
        }

        public static DateTime GetNewestDate(string dir, string pattern, out string file, params string[] ignoreExtensions)
        {
            file = null;
            var result = new List<string>();
            Dir(dir, pattern, result);
            var res = DateTime.MinValue;
            foreach (var f in result)
            {
                var ext = Path.GetFileName(f);
                if(ext!=null)
                {
                    if(Array.Exists(ignoreExtensions, e => ext.Equals(e, StringComparison.InvariantCultureIgnoreCase)))
                        continue;
                }

                var chk = File.GetLastWriteTime(f);
                if (chk <= res) 
                    continue;

                res = chk;
                file = f;
            }
            return res;
        }

        public static string GetVb6Path()
        {
            var key = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Basic", "ProductDir",null);

            if (key == null)
                throw new Exception("Can't find vb6.exe");

            return Path.Combine(key, "VB6.EXE");
        }
    }
}